<html>
<head>
  <title>12.代理（Proxy）模式</title>
  <basefont face="微软雅黑" size="2" />
  <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
  <meta name="exporter-version" content="YXBJ Windows/602628 (zh-CN, DDL); Windows/10.0.0 (Win64); EDAMVersion=V2;"/>
  <style>
    body, td {
      font-family: 微软雅黑;
      font-size: 10pt;
    }
  </style>
</head>
<body>
<a name="22995"/>
<h1>12.代理（Proxy）模式</h1>
<div>
<table bgcolor="#D4DDE5" border="0">
<tr><td><b>创建时间：</b></td><td><i>2020/6/28 16:06</i></td></tr>
<tr><td><b>更新时间：</b></td><td><i>2021/2/11 16:48</i></td></tr>
<tr><td><b>作者：</b></td><td><i>Long Life Student</i></td></tr>
</table>
</div>
<br/>

<div>
<span><div><div><div><span style="font-size: 14pt; font-weight: bold;">一、基本描述</span></div><ul><li><div>为其他对象提供一种代理以控制对这个对象的访问。</div></li><li><div>代理模式为另一个对象提供一个替身或占位符以控制对这个对象的访问。</div><div>使用代理模式创建代表对象，让代表对象控制某对象的访问，被代理的对象可以是远程的对象、创建开销大的对象或需要安全控制的对象。</div></li><ul><li><div>代理可以描述成另一个对象的代表</div></li><li><div>代理之所以需要控制访问，是因为客户不知道如何和远程对象沟通。代理模式有许多变体，而这些变体几乎都与访问控制的做法有关。</div></li><ul><li><div>远程代理控制访问远程对象</div></li><li><div>虚拟代理控制访问创建开销大的资源</div></li><li><div>保护代理就权限控制对资源的访问</div></li></ul></ul><li><div>在需要用比较通用和复杂的对象指针代替简单的指针的时候，使用Proxy模式。（是指用对象代替简单的指针，对象起到指针的作用。）</div></li><li><div>代理模式其实就是在访问对象时引入一定程度的间接性，因为这种间接性，可以附加多种用途。</div></li><li><div>代理其实就是真实对象的代表。</div></li><li><div>代理模式是一个使用率非常高的设计模式。代理的应用非常广泛，大到一个系统框架、企业平台，小到代码片段、事务处理，用途很多。</div></li><li><div>代理模式也叫做委托模式，是一项基本设计技巧，许多其他的模式，如状态模式，策略模式，访问者模式本质上是在更特殊的场合采用了委托模式。</div></li><li><div>一个代理类可以代理多个被委托者或被代理者，因此一个代理类具体代理哪个真实主题角色，是由场景类决定的。</div></li><li><div>代理根据其种类，在适当的时候向RealSubjuect转发请求。</div></li><li><div>代理不仅仅可以有自己的运算方法，通常情况下代理的职责并不一定单一，它可以组合其他的真是角色，也可以实现自己的职责，比如计算费用。代理类可以为真实角色预处理消息、过滤消息、消息转发、事后处理消息等功能。一个代理类可以代理多个真实角色，并且真实角色之间可以有耦合关系。</div></li><li><div>当无法直接访问某个对象或访问某个对象存在困难时可以通过一个代理对象来间接访问，为了保证客户端的透明性，所访问的真实对象与代理对象需要实现相同的接口。</div></li><li><div>由于某些因素，客户端不想或不能直接访问一个对象，此时可通过“代理”的第三者实现间接访问。</div></li><li><div>代理模式引入了一个新的代理对象，代理对象在客户端对象和目标对象之间起到中介的作用，它去掉用户不能看到的内容和服务或者增添客户需要的额外的新服务。</div></li><li><div>通过引入一个新的对象来实现对真实对象的操作或者将新的对象作为真实对象的一个替身，这种实现机制即为代理模式，通过引入代理对象来间接访问一个对象，这就是代理模式的动机。</div></li><li><div>代理的目标是控制和管理访问。</div></li></ul><div><span style="font-size: 14pt; font-weight: bold;">二、图例</span></div><div style="text-align: center;"><img src="12.代理（Proxy）模式_files/Image.png" type="image/png" data-filename="Image.png" width="483"/></div><ul><li><div>Proxy：保存一个引用使得代理可以访问实体。若RealSubject和Subject的接口相同，Proxy会引用Subject。提供一个与Subject的接口相同的接口，这样代理就可以用来代替实体。控制对实体的存取，并可能负责创建和删除它。委托类、代理类，负责对真实角色的应用，把所有抽象主题类定义的方法限制委托给真实主题角色实现，并且在真实主题处理完毕前后做预处理和善后工作。</div><div>Proxy持有Subject的引用，所以必要时它可以将请求转发给Subject。</div><div>创建RealSubject，通常由Proxy负责。</div><div>客户和RealSubject的交互都必须通过Proxy，因为Proxy和RealSubject实现相同的接口，所以任何用到RealSubject的地方，都可以用Proxy取代。</div></li><li><div>Subject：定义RealSubject和Proxy的共用接口，这样就在任何使用RealSubject的地方都可以使用Proxy。可以是抽象类也可以是接口，最普通的业务类型定义。引入这一层抽象的原因是为了让客户端能够一致的对待真实对象和代理对象。</div></li><li><div>RealSubject：定义Proxy所代表的实体。即被代理的真实实体。被委托的角色，被代理的角色，是业务逻辑的具体执行者。</div><div>RealSubject通常是真正做事的对象，它是被proxy代理和访问控制的对象。</div></li></ul><div><span style="font-size: 14pt; font-weight: bold;">三、优缺点</span></div><div><span style="font-weight: bold;">优点</span></div><ul><li><div>职责清晰：真实角色是实现实际的业务逻辑，不用关心其他非本职责的事务，通过后期的代理完成一件事务。</div></li><li><div>高扩展性：具体主题角色随时会发生变化，只要其仍按接口实现，代理类完全可以在不修改的情况下使用。</div></li><li><div>智能化：体现为动态代理。</div></li><li><div>能够协调调用者和被调用者，在一定程度上降低了系统的耦合度。</div></li><li><div>客户端可以针对抽象主题角色进行编程，增加和替换代理类无需修改源代码，符合开闭原则，系统具有较好的灵活性和可扩展性。</div></li></ul><div><span style="font-weight: bold;">缺点</span></div><ul><li><div>由于在客户端和真实主题之间增加了代理对象，因此有些类型的代理模式可能会造成请求的处理速度变慢，如保护代理。</div></li><li><div>实现代理模式需要额外的工作，而且有些代理模式的实现过程较为复杂，如远程代理。</div></li></ul><div><span style="font-size: 14pt; font-weight: bold;">四、常见的代理形式</span></div><div><span style="font-size: 10pt; font-weight: bold;">      </span><span style="font-size: 10pt;">真实世界中，代理模式有很多变体，这些变体都有共通点：都会将客户对主题施加的方法调用拦截下来。</span></div><ol><li><div><span style="font-weight: bold;">远程代理（Remote Proxy）</span></div></li></ol><ul><li><div>为一个对象在不同的地址空间提供局部代表。（代理在这里的意义是，为一个远程资源做本地的代替。）</div></li><li><div>远程代理实际上就是“远程对象的本地代表”</div></li><ul><li><div>远程对象：这是一种对象，是在不同的地址空间运行的远程对象</div></li><li><div>本地代表：是一种可以由本地方法调用的对象，其行为会转发到远程对象中</div></li></ul></ul><div style="margin-left: 40px;">客户对象所做的就像是在做远程方法调用，但其实只是调用本地堆中的“代理”对象上的方法 ，再由代理处理所有网络通信的底层细节。</div><ul><li><div>Remote Proxy对象负责对请求及其参数进行编码，并向不同地址空间中的实体发送已编码的请求。</div></li><li><div>远程代理附加的间接性可以隐藏一个对象存在于不同地址空间的事实。</div></li><li><div>远程代理不包含对实体的直接引用，而只是一个间接引用。如“主机ID，主机上的局部地址”。</div></li><li><div>为一个位于不同的地址空间的对象提供一个本地的代理对象，这个不同的地址空间可以是在同一台主机中，也可以在另一台主机中，又称为大使。</div></li><li><div>远程代理设计复杂，封装了底层网络通信和对远程对象的调用。</div></li><li><div>远程代理使客户端可以访问在远程主机上的对象，代理可以将网络细节隐藏起来，使客户端不必考虑网络的存在。</div></li><li><div>客户端完全可以认为被代理的远程业务对象是在本地而不是在远程，而远程代理对象承担了大部分的网络通信工作，并负责对远程业务方法的调用。</div></li><li><div>客户端无法直接访问远程主机中的业务对象，远程业务对象在本地主机中有一个代理对象，代理对象负责对远程业务对象的访问和网络通信，对于客户端对象而言是透明的。</div></li><li><div>客户端无需关心实现具体业务的是谁，只需按照服务接口定义的方式直接与本地主机中的代理对象交互即可。</div></li><li><div><span style="font-weight: bold;">优点</span>：为位于两个不同地址空间的对象的访问提供了一种实现机制，可以将一些消耗资源较多的对象和操作移至性能更好的计算机上，提供系统整体运行效率。</div></li></ul><ol start="2"><li><div><span style="font-weight: bold;">虚代理（Virtual Proxy）</span></div></li></ol><ul><li><div>根据需要创建开销很大的对象。（按需创建）</div></li><li><div>虚拟代理作为创建开销大的对象的代表。虚拟代理经常直到我们真正需要一个对象的时候才创建它。当对象在创建前和创建中时，由虚拟代理来扮演对象的替身。对象创建后，代理就会将请求直接委托给对象。</div></li><li><div>Virtulal Proxy可以缓存实体的附加信息，以便延迟对它的访问。</div></li><li><div>虚代理附加的间接性可以进行近行最优化，例如根据要求创建对象。</div></li><li><div>虚代理开始的时候使用一个间接引用，例如一个文件名，但最终将获取并使用一个直接引用。</div></li><li><div>如果需要创建一个资源消耗较大的对象，先创建一个消耗相对较小的对象来表示，真实对象只在需要时才会被真正创建。</div></li><li><div>对于一些占用系统资源较多或加载时间较长的对象，可以给这些对象提供一个虚代理。</div></li><li><div>在真实对象创建成功之前，虚代理扮演真实对象的替身，在真实对象创建成功之后，虚代理将用户请求转发给真实对象。</div></li><li><div>以下两种情况下可以使用虚代理：</div></li></ul><div style="padding-left: 30px;">a. 由于对象本身的复杂性或网络等原因导致一个对象需要较长的加载时间，此时可以用一个加载时间相对较短的代理对象来代表真实对象。</div><div style="padding-left: 30px;">在实现时可以结合多线程技术，一个线程用于显示代理对象，其他线程用于加载真实对象。在程序启动时，用代理对象代替真实对象初始化，大大加快了启动时间，后续需要真实对象时，由代理对象引用，此时真实对象加载完毕。</div><div style="padding-left: 30px;">b. 当一个对象的加载十分耗费系统资源时。</div><div style="padding-left: 30px;">虚代理让那些占用大量内存或处理起来非常复杂的对象推迟到使用他们的时候才创建，而在此之前用一个相对来说占用资源较少的代理对象来引用真实对象。</div><ul><li><div>虚代理都是用一个“虚假”的代理对象来表达真实对象，通过代理对象来间接引用真实对象，可以在一定程度上提高系统的性能。</div></li><li><div><span style="font-weight: bold;">优点</span>：通过一个消耗资源较少的对象来代表一个消耗资源较多的对象，可以在一定程度上节省系统运行开销。</div></li></ul><ol start="3"><li><div><span style="font-weight: bold;">保护代理（Protect Proxy）</span></div></li></ol><ul><li><div>控制对原始对象的访问。</div></li><li><div>保护代理是一种根据访问权限决定客户可否访问对象的代理。</div></li><li><div>保护代理用于对象应该有不同的访问权限的时候。（也是代替，所有人通过代理访问对象。）</div></li><li><div>Protection Proxy检查调用者是否具有实现一个请求所必需的访问权限。</div></li><li><div>保护代理附加的间接性允许在访问一个对象时有一些附加的内务处理。</div></li><li><div>控制对一个对象的访问，可以给不同的用户提供不同级别的使用权限。</div></li><li><div><span style="font-weight: bold;">优点</span>：可以控制对一个对象的访问权限为不同的用户提供不同级别的使用权限。</div></li></ul><ol start="4"><li><div><span style="font-weight: bold;">智能指引代理（Smart Reference Proxy）</span></div></li></ol><ul><li><div>取代了简单的指针，在访问对象时执行一些附加操作。</div></li><li><div>当主题被引用时，进行额外的动作，例如计算一个对象被引用的次数。</div></li><li><div>典型用途包括：1）对指向实际对象的引用计数，这样当该对象没有引用时，可以自动释放它（也称为Smart_Pointer）。2）当第一次引用一个持久对象时，将它装入内存。3）在访问一个实际对象前，检查是否已经锁定了它，以确保其他对象不能改变它（线程安全）。</div></li><li><div>智能指引代理附加的间接性允许在访问一个对象时有一些附加的内务处理。</div></li><li><div>当一个对象被引用时，提供一些额外的操作，如将对象被调用次数记录下来等。</div></li></ul><ol start="5"><li><div><span style="font-weight: bold;">缓冲代理（Cache Proxy）</span></div></li></ol><ul><li><div>为某一个目标操作的结果提供临时的存储空间，以便多个客户端可以共享这些结果。从而可以避免某些方法的重复执行，优化系统性能。</div></li><li><div>常见于web服务器代理，以及内容管理与出版系统。</div></li><li><div>为开销大的运算结果提供暂时的存储：它也允许多个客户共享结果，以减少计算或网络延迟。</div></li><li><div><span style="font-weight: bold;">优点</span>：为某一个操作的结果提供临时的缓存存储空间，以便在后续使用中能够共享这些结果，优化系统性能，缩短执行时间。</div></li></ul><ol start="6"><li><div><span style="font-weight: bold;">其他衍生代理</span></div></li></ol><div style="margin-left: 40px;">1）<span style="font-weight: bold;">Copy-on-write代理</span></div><div style="margin-left: 40px;">- Copy-on-write代理于根据需要创建对象有关（虚代理）。</div><div style="margin-left: 40px;">- 用来控制对象的复制，方法是延迟对象的复制，直到客户真的需要为止。这是虚拟代理的变体。</div><div style="margin-left: 40px;">- 拷贝一个庞大而复杂的对象是一种开销很大的操作，如果这个拷贝根本没有被修改，那么这些开销就没有必要。用代理延迟这一拷贝过程，可以保证只有当这个对象被修改的时候才对它进行拷贝。</div><div style="margin-left: 40px;">- 在实现Copy-on-write时必须对实体进行引用计数。拷贝代理仅会增加引用计数。只有当用户请求一个修改该实体的操作时，代理才会真正的拷贝它。在这种情况下，代理还必须减少实体的引用计数。当引用的树木为零时，这个实体将被删除。</div><div style="margin-left: 40px;">- Copy-on-write可以大幅度的降低拷贝庞大实体时的开销。</div><div style="margin-left: 40px;">- 是虚代理的一种，把复制操作延迟到只有在客户端真正需要的时候才执行。一般来说，对象的深克隆是一个开销较大的操作，Copy-on-write代理可以让这个操作延迟，只有对象被用到的时候才被克隆。</div><div style="margin-left: 40px;">2）<span style="font-weight: bold;">防火墙代理（Fire Wall Proxy）</span></div><div style="margin-left: 40px;">- 保护目标不被恶意用户接近。</div><div style="margin-left: 40px;">- 常见于公司的防火墙系统。</div><div style="margin-left: 40px;">- 控制网络资源的访问，保护主题免于“坏客户”的侵害。</div><div style="margin-left: 40px;">3）<span style="font-weight: bold;">同步化代理（Synchronization Proxy）</span></div><div style="margin-left: 40px;">- 使几个用户能够同时使用一个对象而没有冲突。</div><div style="margin-left: 40px;">- 常见于JavaSpaces，为分散式环境内的潜在对象集合提供同步访问控制。</div><div style="margin-left: 40px;">- 在多线程的情况下为主题提供安全的访问。</div><div style="margin-left: 40px;">4）<b>复杂隐藏</b><span style="font-weight: bold;">代理（Complexity Hiding Proxy）</span></div><div style="margin-left: 40px;">- 用来隐藏一个类的复杂集合的复杂度，并进行访问控制。</div><div style="margin-left: 40px;">- 也称为外观代理（Facade Proxy），复杂隐藏代理和外观模式是不一样的，因为代理控制访问，而外观模式只提供另一组接口。</div><div><span style="font-size: 14pt; font-weight: bold;">五、可能的改进</span></div><ul><li><div><a href="https://github.com/PhycheLiu/DesignPattern_Study-Test/tree/master/Java/src/com/company/proxy/DynamicProxy">动态代理。</a>动态代理在实现阶段不用关心代理谁，而在运行阶段才指定代理哪个对象，相对而言，其他的代理方式是静态代理。面向横切面的编程（AOP）其核心就是采用了动态代理机制。</div></li></ul><div style="margin-left: 40px;">- 动态代理的典型应用是Spring AOP；</div><div style="margin-left: 40px;">- 传统代理模式中，真实主题角色必须是事先已经存在的，并将其作为代理对象的内部成员属性。</div><div style="margin-left: 40px;">- 动态代理主要解决：如何在事先不知道真实主题角色的情况下使用代理主题角色。</div><div style="margin-left: 40px;">- 动态代理在实现阶段不用关心代理谁，而在运行阶段才指定代理哪个对象，相对而言，其他的代理方式是静态代理。</div><div style="margin-left: 40px;">- 动态代理的主要意图就是解决常说的横切面编程问题，在不改变已有代码结构的情况下，增强或控制对象的行为。</div><div style="margin-left: 40px;">- 在学习AOP框架时，认识清楚几个名词即可：切面（Aspect）、切入点（JionPoint）、通知（Advice）、织入（Weave）。</div><div style="margin-left: 40px;">Java在java.lang.reflect包中有自己的代理支持，利用这个包你可以在运行时动态的创建一个代理类，实现一个或多个接口，并将方法的调用转发到你所指定的类，因为实际的代理类是在运行时创建的，这个Java技术就是动态代理。</div><div style="margin-left: 40px;">动态代理类图如下。</div><div style="margin-left: 40px;"><img src="12.代理（Proxy）模式_files/Image [1].png" type="image/png" data-filename="Image.png"/></div><div style="margin-left: 40px;">因为Java已经为你创建了Proxy类，所以你需要有办法来告诉Proxy类你要做什么。你不能像以前一样把代码放在Proxy类中，因为 Proxy不是你直接实现的。既然这样的代码不能放在Proxy类中，那么要放在哪里？放在 Invocation Handler中。 InvocationHandler的工作是响应代理的任何调用。你可以把InvocationHandler想成是代理收到方法调用后，请求做实际工作的对象。</div><div style="margin-left: 40px;">动态代理之所以被称为动态，是因为运行时才将它的类创建出来。代码开始执行时，还没有proxy类，它是根据需要从你传入的接口集创建的。</div><ul><li><div>代理模式与工厂模式联合使用</div><div>一个常用的技巧是提供一个工厂，实例化并返回主题。因为这是在工厂方法内发生的，我们可以用代理包装主題再返回，而客户不知道也不在乎他使用的是代理还是真东西。</div></li></ul><div><span style="font-size: 14pt; font-weight: bold;">六、个人理解</span></div><ul><li><div>代理模式思路比实现重要，且在现代编程过程中使用异常频繁。</div></li><li><div>代理模式有很多典型场景，主要有：远程代理，虚拟代理，保护代理，缓冲代理，智能引用代理。</div></li></ul><div style="margin-left: 40px;">基于以上几个代理衍生出：Copy-on-write代理，防火墙代理，同步化代理</div></div><div style="margin-left: 40px;">以及代理模式的重要扩展：动态代理。</div><ul><li><div>代理归根结底是<span style="background-color: rgb(255, 250, 165); color: rgb(227, 0, 0);-evernote-highlight:true;">代替、替换</span>的意思，万变不离其宗，代理实际上就是用另一个对象代替一个还不方便或需扩展的对象。</div></li><li><div>有的代理在客户端定义具体主题的对象，有的代理在代理的构造函数中定义具体主题的对象。</div></li></ul><div style="margin-left: 40px;">个人认为应该具体场景具体分析，按需要进行选择，没有一定之规。</div><div style="margin-left: 40px;">实际上，<span style="background-color: rgb(255, 250, 165);-evernote-highlight:true;">个人认为代理模式的思想比实现方法更重要！</span></div><div><br clear="none"/></div></div><div><br/></div></span>
</div></body></html> 